﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

using XnaAux;

namespace PrisonStep
{
    public class AnimationPlayer
    {
        private struct BoneInfo : AnimationClips.Bone
        {
            private int currentKeyframe;     // Current keyframe for bone
            private bool valid;

            private Quaternion rotation;
            private Vector3 translation;

            public int CurrentKeyframe { get { return currentKeyframe; } set { currentKeyframe = value; } }
            public bool Valid { get { return valid; } set { valid = value; } }
            public Quaternion Rotation { get { return rotation; } set { rotation = value; } }
            public Vector3 Translation { get { return translation; } set { translation = value; } }
        }

        private AnimationClips.Clip clip;
        private BoneInfo[] boneInfos;
        private int boneCnt;

        public int BoneCount { get { return boneCnt; } }
        public AnimationClips.Bone GetBone(int b) { return boneInfos[b]; }

        private bool looping = false;
        private double speed = 1.0f;

        private double time = 0;
        public double Time { get { return time; } set { time = value; } }

        /// <summary>
        /// Indicates if the playback should "loop" or not.
        /// </summary>
        public bool Looping { get { return looping; } set { looping = value; } }

        /// <summary>
        /// Indicates if the clip is finished playing
        /// </summary>
        public bool FinishedPlaying { get { return time > clip.Duration; } }

        /// <summary>
        /// Playback speed
        /// </summary>
        public double Speed { get { return speed; } set { speed = value; } }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="clip"></param>
        public AnimationPlayer(AnimationClips.Clip clip)
        {
            this.clip = clip;
        }

        /// <summary>
        /// Initialize for use
        /// </summary>
        public void Initialize()
        {
            boneCnt = clip.Keyframes.Length;
            boneInfos = new BoneInfo[boneCnt];

            time = 0;
            for (int b = 0; b < boneCnt; b++)
            {
                boneInfos[b].CurrentKeyframe = -1;
                boneInfos[b].Valid = false;
            }
        }

        /// <summary>
        /// Update the clip position
        /// </summary>
        /// <param name="delta">The amount of time that has passed.</param>
        public float Update(double delta)
        {
            time += delta * speed;

            for (int b = 0; b < boneInfos.Length; b++)
            {
                List<AnimationClips.Keyframe> keyframes = clip.Keyframes[b];
                if (keyframes.Count == 0)
                    continue;

                // The time needs to be greater than or equal to the
                // current keyframe time and less than the next keyframe 
                // time.
                while (boneInfos[b].CurrentKeyframe < 0 ||
                    (boneInfos[b].CurrentKeyframe < keyframes.Count - 1 &&
                    keyframes[boneInfos[b].CurrentKeyframe + 1].Time <= time))
                {
                    // Advance to the next keyframe
                    boneInfos[b].CurrentKeyframe++;
                }

                //
                // Update the bone
                //
                
                int c = boneInfos[b].CurrentKeyframe;
                if (c >= 0)
                {
                    AnimationClips.Keyframe start = keyframes[c];
                    AnimationClips.Keyframe end = keyframes[(c + 1) % keyframes.Count];
                    UpdateBone(ref boneInfos[b], start, end);
                }
            }

            //restart animation if needed
            if (looping && time >= clip.Duration)
            {
                time = 0;
                for (int b = 0; b < boneInfos.Length; b++)
                {
                    boneInfos[b].CurrentKeyframe = -1;
                }
            }

            //if we want to reset animation manually, we need to know when it ended
            if (time >= clip.Duration)
                return (float)(time - clip.Duration);
            else
                return 0;
        }
        /// <summary>
        /// manages the interpolation for the bone's keyframes
        /// </summary>
        /// <param name="bone"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        private void UpdateBone(ref BoneInfo bone, AnimationClips.Keyframe start, AnimationClips.Keyframe end)
        {

            //just use one keyframes if they are the same
            if ((end.Time - start.Time) <= 0)
            {
                bone.Valid = true;
                bone.Rotation = start.Rotation;
                bone.Translation = start.Translation;
                return;
            }

            float t = (float)((time - start.Time) / (end.Time - start.Time));

            bone.Valid = true;
            bone.Rotation = Quaternion.Slerp(start.Rotation, end.Rotation, t);
            bone.Translation = Vector3.Lerp(start.Translation, end.Translation, t);
        }
    }
}
